 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.osgi.internal.baseadaptor;

 import java.io.File ;
 import java.io.IOException ;
 import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
 import org.eclipse.osgi.internal.resolver.*;
 import org.eclipse.osgi.service.resolver.*;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;

 /**
  * The StateManager manages the system state for the framework. It also provides the implementation
  * to the PlatformAdmin service.
  * <p>
  * Clients may extend this class.
  * </p>
  * @since 3.1
  */
 public class StateManager implements PlatformAdmin, Runnable {
     /**
      * General debug flag
      */
     public static boolean DEBUG = false;
     /**
      * Reader debug flag
      */
     public static boolean DEBUG_READER = false;
     /**
      * PlatformAdmin debug flag
      */
     public static boolean DEBUG_PLATFORM_ADMIN = false;
     /**
      * PlatformAdmin resolver debug flag
      */
     public static boolean DEBUG_PLATFORM_ADMIN_RESOLVER = false;
     /**
      * Monitor PlatformAdmin debug flag
      */
     public static boolean MONITOR_PLATFORM_ADMIN = false;
     /**
      * System property used to disable lazy state loading
      */
     public static String PROP_NO_LAZY_LOADING = "osgi.noLazyStateLoading"; //$NON-NLS-1$
 /**
      * System property used to specify to amount time before lazy data can be flushed from memory
      */
     public static String PROP_LAZY_UNLOADING_TIME = "osgi.lazyStateUnloadingTime"; //$NON-NLS-1$
 private long expireTime = 300000; // default to five minutes
 private long readStartupTime;
     private StateImpl systemState;
     private StateObjectFactoryImpl factory;
     private long lastTimeStamp;
     private boolean cachedState = false;
     private File stateFile;
     private File lazyFile;
     private long expectedTimeStamp;
     private BundleContext context;
     private Thread dataManagerThread;

     /**
      * Constructs a StateManager using the specified files and context
      * @param stateFile a file with the data required to persist in memory
      * @param lazyFile a file with the data that may be lazy loaded and can be flushed from memory
      * @param context the bundle context of the system bundle
      */
     public StateManager(File stateFile, File lazyFile, BundleContext context) {
         // a negative timestamp means no timestamp checking
 this(stateFile, lazyFile, context, -1);
     }

     /**
      * Constructs a StateManager using the specified files and context
      * @param stateFile a file with the data required to persist in memory
      * @param lazyFile a file with the data that may be lazy loaded and can be flushed from memory
      * @param context the bundle context of the system bundle
      * @param expectedTimeStamp the expected timestamp of the persisted system state. A negative
      * value indicates that no timestamp checking is done
      */
     public StateManager(File stateFile, File lazyFile, BundleContext context, long expectedTimeStamp) {
         this.stateFile = stateFile;
         this.lazyFile = lazyFile;
         this.context = context;
         this.expectedTimeStamp = expectedTimeStamp;
         factory = new StateObjectFactoryImpl();
     }

     /**
      * Shutsdown the state manager. If the timestamp of the system state has changed
      * @param stateFile
      * @param lazyFile
      * @throws IOException
      */
     public void shutdown(File stateFile, File lazyFile) throws IOException {
         BundleDescription[] removalPendings = systemState.getRemovalPendings();
         if (removalPendings.length > 0)
             systemState.resolve(removalPendings);
         writeState(systemState, stateFile, lazyFile);
         stopDataManager();
     }

     /**
      * Update the given target files with the state data in memory.
      * @param stateFile
      * @param lazyFile
      * @throws IOException
      */
     public void update(File stateFile, File lazyFile) throws IOException {
         BundleDescription[] removalPendings = systemState.getRemovalPendings();
         StateImpl state = systemState;
         if (removalPendings.length > 0) {
             state = (StateImpl) state.getFactory().createState(systemState);
             state.setResolver(getResolver(System.getSecurityManager() != null));
             state.setPlatformProperties(FrameworkProperties.getProperties());
             state.resolve(false);
         }
         writeState(state, stateFile, lazyFile);
         lastTimeStamp = state.getTimeStamp();
         // TODO consider updating the state files for lazy loading
 }

     private void readSystemState(File stateFile, File lazyFile, long expectedTimeStamp) {
         if (stateFile == null || !stateFile.isFile())
             return;
         if (DEBUG_READER)
             readStartupTime = System.currentTimeMillis();
         try {
             boolean lazyLoad = !Boolean.valueOf(FrameworkProperties.getProperty(PROP_NO_LAZY_LOADING)).booleanValue();
             systemState = factory.readSystemState(stateFile, lazyFile, lazyLoad, expectedTimeStamp);
             // problems in the cache (corrupted/stale), don't create a state object
 if (systemState == null || !initializeSystemState()) {
                 systemState = null;
                 return;
             }
             cachedState = true;
             try {
                 expireTime = Long.parseLong(FrameworkProperties.getProperty(PROP_LAZY_UNLOADING_TIME, Long.toString(expireTime)));
             } catch (NumberFormatException nfe) {
                 // default to not expire
 expireTime = 0;
             }
             if (lazyLoad && expireTime > 0)
                 startDataManager();
         } catch (IOException ioe) {
             // TODO: how do we log this?
 ioe.printStackTrace();
         } finally {
             if (DEBUG_READER)
                 System.out.println("Time to read state: " + (System.currentTimeMillis() - readStartupTime)); //$NON-NLS-1$
 }
     }

     private synchronized void startDataManager() {
         if (dataManagerThread != null)
             return;
         dataManagerThread = new Thread (this, "State Data Manager"); //$NON-NLS-1$
 dataManagerThread.setDaemon(true);
         dataManagerThread.start();
     }

     /**
      * Stops the active data manager thread which is used to unload unused
      * state objects from memory.
      */
     public synchronized void stopDataManager() {
         if (dataManagerThread == null)
             return;
         dataManagerThread.interrupt();
         dataManagerThread = null;
     }

     private void writeState(StateImpl state, File stateFile, File lazyFile) throws IOException {
         if (state == null)
             return;
         if (cachedState && !saveNeeded())
             return;
         state.fullyLoad(); // make sure we are fully loaded before saving
 factory.writeState(state, stateFile, lazyFile);
     }

     private boolean initializeSystemState() {
         systemState.setResolver(getResolver(System.getSecurityManager() != null));
         lastTimeStamp = systemState.getTimeStamp();
         return !systemState.setPlatformProperties(FrameworkProperties.getProperties());
     }

     /**
      * Creates a new State used by the system. If the system State already
      * exists then a new system State is not created.
      * @return the State used by the system.
      */
     public synchronized State createSystemState() {
         if (systemState == null) {
             systemState = factory.createSystemState();
             initializeSystemState();
         }
         return systemState;
     }

     /**
      * Reads the State used by the system. If the system State already
      * exists then the system State is not read from a cache. If the State could
      * not be read from a cache then <code>null</code> is returned.
      * @return the State used by the system or <code>null</code> if the State
      * could not be read from a cache.
      */
     public synchronized State readSystemState() {
         if (systemState == null)
             readSystemState(stateFile, lazyFile, expectedTimeStamp);
         return systemState;
     }

     /**
      * Returns the State used by the system. If the system State does
      * not exist then <code>null</code> is returned.
      * @return the State used by the system or <code>null</code> if one
      * does not exist.
      */
     public State getSystemState() {
         return systemState;
     }

     /**
      * Returns the cached time stamp of the system State. This value is the
      * original time stamp of the system state when it was created or read from
      * a cache.
      * @see State#getTimeStamp()
      * @return the cached time stamp of the system State
      */
     public long getCachedTimeStamp() {
         return lastTimeStamp;
     }

     public boolean saveNeeded() {
         return systemState.getTimeStamp() != lastTimeStamp || systemState.dynamicCacheChanged();
     }

     /**
      * @see PlatformAdmin#getState(boolean)
      */
     public State getState(boolean mutable) {
         return mutable ? factory.createState(systemState) : new ReadOnlyState(systemState);
     }

     /**
      * @see PlatformAdmin#getState()
      */
     public State getState() {
         return getState(true);
     }

     /**
      * @see PlatformAdmin#getFactory()
      */
     public StateObjectFactory getFactory() {
         return factory;
     }

     /**
      * @see PlatformAdmin#commit(State)
      */
     public synchronized void commit(State state) throws BundleException {
         throw new IllegalArgumentException ("PlatformAdmin.commit() not supported"); //$NON-NLS-1$
 }

     /**
      * @see PlatformAdmin#getResolver()
      */
     public Resolver getResolver() {
         return getResolver(false);
     }

     private Resolver getResolver(boolean checkPermissions) {
         return new org.eclipse.osgi.internal.module.ResolverImpl(context, checkPermissions);
     }

     /**
      * @see PlatformAdmin#getStateHelper()
      */
     public StateHelper getStateHelper() {
         return StateHelperImpl.getInstance();
     }

     public void run() {
         long timeStamp = lastTimeStamp; // cache the original timestamp incase of updates
 while (true) {
             try {
                 Thread.sleep(expireTime);
             } catch (InterruptedException e) {
                 return;
             }
             if (systemState != null)
                 synchronized (systemState) {
                     if (timeStamp == systemState.getTimeStamp() && !systemState.dynamicCacheChanged())
                         systemState.unloadLazyData(expireTime);
                 }
         }
     }
 }

